/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * String Collection Class
 *		An extended version of the <see cref="System.Collections.Specialized.StringCollection"/> class to 
 * provide extra functionality.
 *--------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
 * System Imports
 *--------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using Adaptive.Foundation.Properties;

namespace Adaptive.Foundation.Collections
{
    /// <summary>
    /// An extended version of the <see cref="System.Collections.Specialized.StringCollection"/> class to 
    /// added parsing and sorting routines.
    /// </summary>
    public class StringCollection : System.Collections.Specialized.StringCollection, IDisposable, IClearable, INamedInstance  
    {
        /*----------------------------------------------------------------------------
         * Public Event Declarations
         *--------------------------------------------------------------------------*/
        #region  Public Event Declarations
        /// <summary>
        /// Occurs when the instance's name value is changed.
        /// </summary>
        public event EventHandler NameChanged;
        #endregion
        /*----------------------------------------------------------------------------
         * Private Member Declarations
         *--------------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// Name of the collection.
        /// </summary>
        private string _name = string.Empty;
        /// <summary>
        /// Enumeration position.
        /// </summary>
        private int _location = -1;
        #endregion

        /*----------------------------------------------------------------------------
		 * Constructor/Dispose/Destructor Methods
		 *--------------------------------------------------------------------------*/
        #region Constructor/Dispose/Destructor Methods
        /// <summary>
        /// Initializes the class to an empty collection.
        /// </summary>
        /// <remarks>
        /// This is the default constructor.
        /// </remarks>
        public StringCollection()
            : base()
        {
        }
        /// <summary>
        /// Initializes with the contents of a string parsed with a delimiter.
        /// </summary>
        /// <param name="text">The original string to be parsed into parts and inserted
        /// into the collection.</param>
        /// <param name="delimiter">The delimiter used to parse the original string.</param>
        public StringCollection(string text, string delimiter)
            : base()
        {
            Parse(text, delimiter);
        }
        /// <summary>
        /// Initializes the class with the contents of the supplied string array.
        /// </summary>
        /// <param name="data">An array whose contents are to be copied into the collection.</param>
        public StringCollection(string[] data)
            : base()
        {
            AddRange(data);
        }
        /// <summary>
        /// Initializes an new instance of the class.
        /// </summary>
        /// <param name="capacity">The size of the collection.</param>
        public StringCollection(int capacity)
            : base()
        {
            CreateEmptyContent(capacity);
        }
        /// <summary>
        /// Initializes the class with the contents of another <see cref="StringCollection"/> object.
        /// </summary>
        /// <param name="data">A <see cref="StringCollection"/> whose contents are to be 
        /// copied into the collection.</param>
        public StringCollection(StringCollection data)
            : base()
        {
            AddRange(data);
        }
        /// <summary>
        /// Disposes of internally allocated objects.
        /// </summary>
        /// <param name="disposing">A value indicating whether the object is being disposed.</param>
        protected virtual void Dispose(bool disposing)
        {
            base.Clear();
            _name = null;
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Disposes of internally allocated objects.
        /// </summary>
        public virtual void Dispose()
        {
            Dispose(true);
        }
        /// <summary>
        /// Class destructor.
        /// </summary>
        ~StringCollection()
        {
            Dispose(false);
        }
        #endregion

        /*----------------------------------------------------------------------------
		 * Public Properties
		 *--------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// The indexer property for this collection.
        /// </summary>
        /// <paramref name="iIndex">The index of the string value to be read or set.</paramref>
        /// <value>
        /// Gets or sets a string value at a position in the collection.
        /// </value>
        /// <remarks>
        /// When reading the property, indices outside the range of the collection may
        /// be referenced without exception - the property returns a blank string.  However,
        /// an exception will occur if an invalid index is referenced when setting the
        /// property value.
        /// </remarks>
        public new string this[int index]
        {
            //Returns a string item in the collection at the specified index.
            get
            {
                if ((index >= 0) && (index < Count))
                    return base[index];
                else
                    return string.Empty;
            }
            set
            {
                if ((index >= 0) && (index < Count))
                    base[index] = value;
            }
        }
        /// <summary>
        /// A name associated with this object.
        /// </summary>
        /// <value>A string name to be associated with the collection.</value>
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        /// <summary>
        /// Gets or sets the value of the current enumeration.
        /// </summary>
        /// <value>
        /// The string value at the current enumeration position.
        /// </value>
        /// <remarks>
        /// This property is used in conjunction with the <see cref="MoveFirst"/>, 
        /// <see cref="MoveNext"/>, <see cref="MovePrevious"/>, <see cref="MoveLast"/>
        /// methods and the <see cref="Eof"/> property.  The value returned by this 
        /// property when <see cref="Eof"/> is <b>true</b> or <see cref="MoveFirst"/> has
        /// not yet been called is a blank string.
        /// </remarks>
        public string Value
        {
            get
            {
                if ((_location > -1) && (_location < Count))
                    return base[_location];
                else
                    return string.Empty;
            }
            set
            {
                if ((_location > -1) && (_location < Count))
                    base[_location] = value;
            }
        }
        /// <summary>
        /// Gets a flag indicating whether or not the end of the collection has been reached.
        /// </summary>
        /// <value>
        /// If enumeration methods have reached the end of the collection, the collection is
        /// empty, or a call to <see cref="MoveFirst"/> has not been made, the property returns
        /// <b>true</b>.  Otherwise, the property returns <b>false</b>.
        /// </value>
        /// <seealso cref="Value">Value property</seealso>
        /// <seealso cref="MoveFirst">MoveFirst method</seealso>
        /// <seealso cref="MoveNext">MoveNext method</seealso>
        /// <seealso cref="MovePrevious">MovePrevious method</seealso>
        /// <seealso cref="MoveLast">MoveLast method</seealso>
        public bool Eof
        {
            get
            {
                if ((_location == Count) || (_location == -1))
                    return true;
                else
                    return false;
            }
        }
        #endregion

        /*----------------------------------------------------------------------------
		 * Public Methods/Functions
		 *--------------------------------------------------------------------------*/
        #region Public Methods/Functions
        /// <summary>
        /// Adds the contents of the supplied collection to the current collection.
        /// </summary>
        /// <param name="data">
        /// The <see cref="StringCollection"/> whose contents are to be appended.
        /// </param>
        public virtual void AddRange(StringCollection data)
        {
            base.AddRange(data.ToArray());
        }
        /// <summary>
        /// Copies the contents of a <see cref="StringCollection"/> object to this object.
        /// </summary>
        /// <param name="itemToCopy">The <see cref="StringCollection"/> object to be copied.</param>
        /// <remarks>When this method is called, the current contents of the collection are erased first.</remarks>
        public virtual void Copy(StringCollection itemToCopy)
        {
            //Copies the contents of another string collection to this instance.
            //Parameter: colData - A ParsingStringCollection to copy data from.

            //Clear the local collection.
            Clear();

            //Add the items from the other collection.
            AddRange(itemToCopy);

        }
        /// <summary>
        /// Adds a number of blank strings to the collection.
        /// </summary>
        /// <param name="size">The number of items to be created in the collection.</param>
        /// <remarks>This method erases the current content of the collection, and inserts the
        /// specified number of blank strings into the collection.</remarks>
        public void CreateEmptyContent(int size)
        {
            //Clears the collection, and creates a specified number of blank 
            //strings in the collection.
            string[] data = null;

            data = new string[size];
            data.Initialize();
            AddRange(data);
            data = null;
        }
        /// <summary>
        /// Moves the enumeration pointer to the start of the collection.
        /// </summary>
        public void MoveFirst()
        {
            //Moves the integer location pointer to the first item in the collection.

            if (Count == 0)
                _location = -1;
            else
                _location = 0;
        }
        /// <summary>
        /// Moves the enumeration pointer to the next item in the collection.
        /// </summary>
        public void MoveNext()
        {
            //Moves the integer location pointer to the next item in the collection.
            _location++;
            if (_location >= Count)
                _location = -1;
        }
        /// <summary>
        /// Moves the enumeration pointer to the last item in the collection.
        /// </summary>
        public void MoveLast()
        {
            //Moves the integer location pointer to the last item in the collection.
            _location = Count - 1;
        }
        /// <summary>
        /// Moves the enumeration pointer to the previous item in the collection.
        /// </summary>
        public void MovePrevious()
        {
            //Moves the integer location pointer to the previous item in the collection.
            _location--;
            if (_location < -1)
                _location = -1;
        }
        /// <summary>
        /// Parses a string with the given delimiter and inserts the string parts into the collection.
        /// </summary>
        /// <param name="original">The original string to be parsed.</param>
        /// <param name="delimiter">The string delimiter to parse the original string with.</param>
        /// <remarks>
        /// This method clears the current contents of the collection before parsing the given string.
        /// The supplied original string is then parsed with the supplied delimiter, however, strings 
        /// in double-quotatin marks ("") are preserved, even if a delimiter is present.
        /// </remarks>
        /// <example>
        /// <code>
        /// Dim colData As StringCollection
        /// Dim sOrig As String
        /// 
        /// sOrig = "This is a string " &amp; Chr(34) &amp; "to be tested" &amp; Chr(34) &amp; "."
        /// colData = new StringCollection()
        /// colData.Parse(sOrig, " ")
        /// </code>
        /// The result of the <b>Parse</b> method is:
        /// 
        /// sOrig(0) = "This"
        /// sOrig(1) = "is"
        /// sOrig(2) = "a"
        /// sOrig(3) = "string"
        /// sOrig(4) = "to be tested"
        /// sOrig(5) = "."
        /// </example>
        public void Parse(string original, string delimiter)
        {
            //Parses a delimited string into its parts using the specified delimiter.
            //Parameters:    sOrig - The string to be parsed.
            //               sDelimiter - The delimiter to use.

            int position = 0;
            int startDel = 0;
            int startQuote = 0;
            int end = 0;
            string item = string.Empty;

            if (original.Length > 0)
            {
                //Clear the contents of the collection.
                Clear();

                //If the delimiter is multiple characters, use the Split() function, and
                //then append each item in the returned array to the collection.
                if (delimiter.Length > 1)
                {
                    if (original.Substring(original.Length - 2, 2) == delimiter)
                        original = original.Substring(0, original.Length - 2);

                    AddRange(original.Split(delimiter.ToCharArray()));
                }
                else
                {
                    //In this case, we are parsing not only using the specified 
                    //delimiter, but also with quote marks.  Anything between
                    //two double-quotes (") is an item to be added without regard to 
                    //the original delimiter, as well as anything between specified 
                    //delimiters (outside of quotes).
                    do
                    {
                        //Locate the next delimiter and next quote character positions
                        startDel = original.IndexOf(delimiter, position);
                        startQuote = original.IndexOf(Resources.DoubleQuote, position);

                        //If there is un-parsed text remaining, and no more quotes
                        //or delimiters, then save the last text in the collection
                        //and exit this loop.
                        if ((startQuote == -1) && (startDel == -1) && (position < original.Length))
                        {
                            item = original.Substring(position, (original.Length - position));
                            Add(item);
                            position = original.Length;
                        }
                        else
                        {
                            //If the quote comes first, then
                            if ((startQuote != -1) && (startQuote < startDel))
                            {
                                //If there is un-parsed text preceding the leading quote, 
                                //save that to the collection.
                                if (position < startQuote)
                                {
                                    item = original.Substring(position, (startQuote - position));
                                    Add(item);
                                    position = startQuote;
                                }
                                //Locate the next quote character
                                end = original.IndexOf(Resources.DoubleQuote, startQuote + 1);

                                //Get the substring
                                item = original.Substring(startQuote + 1, (end - (startQuote + 1)));

                                //Set the next position to the character after the end quote.
                                position = end + 1;

                                //If the end quote is immediately followed by a delimiter, 
                                //advance one position.
                                if ((position >= original.Length) || (original[position].ToString() == delimiter))
                                    position++;
                            }
                            else
                            {
                                //Get the substring.
                                if (startDel == -1)
                                {
                                    item = original.Substring(position + 1, (original.Length - (position + 2)));
                                    startDel = original.Length;
                                }
                                else
                                    item = original.Substring(position, (startDel - position));

                                //Set the next position to the character after the delimiter.
                                position = startDel + 1;
                            }

                            //Add the item to the collection
                            Add(item);
                        }
                    } while ((position < original.Length));
                }
            }
        }
        /// <summary>
        /// Removes the first <i>x</i> number of strings from the collection.
        /// </summary>
        /// <param name="numberOfItems">The number of strings to be removed.</param>
        /// <remarks>This method removes the specified number of strings from the start
        /// of the collection.  If <i>iNumItems</i> is greater than or equal to the count of items
        /// in the collection, the entire collection is cleared.</remarks>
        public void RemoveFirst(int numberOfItems)
        {
            //Removes the first item in the collection a specified number of times.
            //(This allows the caller to "erase" items in the collection by removing
            //the first (easiest accessible) object in the collection).
            //Parameter: iNumItems - The number of items to be removed.

            int count = 0;

            //Modify the parameter, if neccessary.
            if (numberOfItems > Count)
                numberOfItems = Count;

            //For the specified number of iterations, remove the first item in the
            //collection.
            for (count = 0; count < numberOfItems; count++)
                RemoveAt(0);

        }
        /// <summary>
        /// Removes the last <i>x</i> number of strings from the collection.
        /// </summary>
        /// <param name="numberOfItems">The number of strings to be removed.</param>
        /// <remarks>This method removes the specified number of strings from the end
        /// of the collection.  If <i>iNumItems</i> is greater than or equal to the count of items
        /// in the collection, the entire collection is cleared.</remarks>
        public void RemoveLast(int numberOfItems)
        {
            //Removes the first item in the collection a specified number of times.
            //(This allows the caller to "erase" items in the collection by removing
            //the last object in the collection).
            //Parameter: iNumItems - The number of items to be removed.

            int count = 0;

            //Modify the parameter, if neccessary.
            if (numberOfItems > Count)
                numberOfItems = Count;

            //For the specified number of iterations, remove the last item in the
            //collection.
            for (count = 0; count < numberOfItems; count++)
                RemoveAt(Count - 1);

        }
        /// <summary>
        /// Gets the contents of the collection as a string array.
        /// </summary>
        /// <returns>A string array containing all the strings in the collection.</returns>
        public string[] ToArray()
        {
            //Copies the contents of the collection to a string array.
            //Return Value:  String() - A string array containing duplicate contents of this collection.
            string[] returnValue = null;

            returnValue = new string[Count];
            CopyTo(returnValue, 0);
            return returnValue;

        }
        /// <summary>
        /// Removes a string from the collection at the specified index.
        /// </summary>
        /// <param name="index">The collection index of the string to be removed.</param>
        public void Remove(int index)
        {
            base.RemoveAt(index);
        }
        /// <summary>
        /// Removes all empty strings from the collection.
        /// </summary>
        public void RemoveEmpty()
        {
            int count = 0;

            for (count = Count - 1; count >= 0; count--)
            {
                if (this[count].Trim() == string.Empty)
                    Remove(count);
            }
        }
        /// <summary>
        /// Sorts the contents of the collection.
        /// </summary>
        /// <remarks>
        /// This method performs an alphanumeric sort on the contents of the collection in
        /// ascending order.  To get a descending list of items after calling this method,
        /// use <see cref="MoveLast"/> and <see cref="MovePrevious"/>.
        /// </remarks>
        public void Sort()
        {
            string[] data = null;

            data = ToArray();
            Array.Sort(data);
            Clear();
            AddRange(data);
            data = null;

        }
        /// <summary>
        /// Returns the contents of the collection as a single string.
        /// </summary>
        /// <param name="delimiter">A string delimiter to use.</param>
        /// <returns>Returns a string containing the entire contents of the collection,
        /// with each string separated by the specified delimiter.  This is the reverse
        /// of the <see cref="Parse"/> method.</returns>
        public string ToString(string delimiter)
        {
            StringBuilder builder = null;
            string returnValue = null;

            builder = new StringBuilder();
            MoveFirst();
            while (!Eof)
            {
                builder.Append(Value + delimiter);
                MoveNext();
            }

            returnValue = builder.ToString();
            builder = null;
            return returnValue;

        }
        /// <summary>
        /// Saves the content of the collection to an open binary file.
        /// </summary>
        /// <param name="writer">
        /// An open <see cref="BinaryWriter"/> object.
        /// </param>
        public void Save(BinaryWriter writer)
        {
            writer.Write(Count);
            MoveFirst();
            while (!Eof)
            {
                writer.Write(Value);
                MoveNext();
            }
        }
        /// <summary>
        /// Loads the content of the collection from an open binary file.
        /// </summary>
        /// <param name="reader">
        /// An open <see cref="BinaryReader"/> object.
        /// </param>
        public void Load(BinaryReader reader)
        {
            int count = 0;			//Iteration counter.
            int length = 0;			//Number of items to load.

            //Clear current contents.
            Clear();

            length = reader.ReadInt32();
            for (count = 0; count < length; count++)
                Add(reader.ReadString());
        }
        #endregion

        /*----------------------------------------------------------------------------
		 * Protected Methods/Functions
		 *--------------------------------------------------------------------------*/
        #region Protected Methods/Functions
        /// <summary>
        /// Raises the <see cref="NameChanged"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments instance.
        /// </param>
        public void OnNameChanged(EventArgs e)
        {
            if (NameChanged != null)
                NameChanged(this, e);
        }
        #endregion

    }
}
